package com.htpay.config.shiro;

import com.htpay.config.shiro.jwt.JwtToken;
import com.htpay.model.common.Constant;
import com.htpay.model.entity.Merchant;
import com.htpay.service.IBaseAPIService;
import com.htpay.service.IMerchantService;
import com.htpay.util.JedisUtil;
import com.htpay.util.JwtUtil;
import com.htpay.util.ObjectConvertUtils;
import com.htpay.util.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;


/**
 * 自定义Realm
 */
@Service
@Slf4j
public class UserRealm extends AuthorizingRealm {

    @Autowired
    @Lazy
    private IMerchantService sysUserService;
    @Autowired
    @Lazy
    private IBaseAPIService sysBaseAPI;

    /**
     * 大坑，必须重写此方法，不然Shiro会报错
     */
    @Override
    public boolean supports(AuthenticationToken authenticationToken) {
        return authenticationToken instanceof JwtToken;
    }

    /**
     * 只有当需要检测用户权限的时候才会调用此方法，例如checkRole,checkPermission之类的
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    /**
     * 默认使用此方法进行用户名正确与否验证，错误抛出异常即可。
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String token = (String) authenticationToken.getCredentials();

        if (token == null) {
            log.info("————————身份认证失败——————————IP地址:  " + ObjectConvertUtils.getIpAddrByRequest(SpringContextUtils.getHttpServletRequest()));
            throw new AuthenticationException("token为空!");
        }
        // 校验token有效性
        return this.checkUserTokenIsEffect(token);
    }


    /**
     * JWTToken刷新生命周期 （实现： 用户在线操作不掉线功能）
     * 1、登录成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样)，缓存有效期设置为Jwt有效时间的2倍
     * 2、当该用户再次请求时，通过JWTFilter层层校验之后会进入到doGetAuthenticationInfo进行身份验证
     * 3、当该用户这次请求jwt生成的token值已经超时，但该token对应cache中的k还是存在，则表示该用户一直在操作只是JWT的token失效了，程序会给token对应的k映射的v值重新生成JWTToken并覆盖v值，该缓存生命周期重新计算
     * 4、当该用户这次请求jwt在生成的token值已经超时，并在cache中不存在对应的k，则表示该用户账户空闲超时，返回用户信息已失效，请重新登录。
     * 注意： 前端请求Header中设置Authorization保持不变，校验有效性以缓存中的token为准。
     * 用户过期时间 = Jwt有效时间 * 2。
     *
     * @param token
     * @return
     */
    public SimpleAuthenticationInfo checkUserTokenIsEffect(String token) throws AuthenticationException {
        // 解密获得username，用于和数据库进行对比
        String account = JwtUtil.getClaim(token, Constant.ACCOUNT);
        log.info("JwtUtil getClaim获取用户username信息: " + account);
        if (account == null) {
            log.error("--------JwtUtil getClaim获取用户username信息为空--------");
            throw new AuthenticationException("token非法无效!");
        }

        // 查询用户信息
        log.info("———校验token是否有效————checkUserTokenIsEffect——————— " + token);
        Merchant merchant = sysBaseAPI.getUserByName(account);
        if (merchant == null) {
            throw new AuthenticationException("用户不存在!");
        }

        // 判断用户状态
        if (merchant.getStatus() != 0) {
            throw new AuthenticationException("账号已被锁定,请联系管理员!");
        }

        // 开始认证，要AccessToken认证通过，且Redis中存在RefreshToken，且两个Token时间戳一致
        if (JwtUtil.verify(token) && JedisUtil.exists(Constant.PREFIX_SHIRO_REFRESH_TOKEN + account)) {
            // 获取RefreshToken的时间戳
            String currentTimeMillisRedis = JedisUtil.getObject(Constant.PREFIX_SHIRO_REFRESH_TOKEN + account).toString();
            // 获取AccessToken时间戳，与RefreshToken的时间戳对比
            if (JwtUtil.getClaim(token, Constant.CURRENT_TIME_MILLIS).equals(currentTimeMillisRedis)) {
                return new SimpleAuthenticationInfo(merchant, token, "userRealm");
            }
        }

        throw new AuthenticationException("Token失效，请重新登录");

    }

    /**
     * 清除当前用户的权限认证缓存
     *
     * @param principals 权限信息
     */
    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }
}
